//The MIT License
//
//Copyright (c) <2011> <GEPAR - Grupo de Ensino e Pesquisa em Agroinformática e Rastreabilidade>
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.

package core;

import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;

/**
 *
 * Principal classe de negócio do software. É responsável por concentrar os dados importantes de uma tabulação. Além
 * disso, é responsável por fazer os cálculos estatísticos suportados pelo software.
 */
public class Tabulacao
{
    int idTabulacao;

    /**
     * Objeto que contém os dados estatísticos de uma tabulação.
     */
    private EstatisticasTabul estatisticasTabul = new EstatisticasTabul();

    /**
     * Referência ao objeto que contém os dados de uma configuração de nova tabulação.
     */
    private ConfigNovaTabul configNovaTabul;
    
    private int idResponsavelTabul;

    /**
     * Nome da pessoa que fez a tabulação dos dados.
     */
    private String responsavelTabul;

    private Date dataTabulacao;

    /**
     * Conjunto de preços da caixa para a tabulação atual
     */
    private LinkedList<Float> precosCaixa = new LinkedList();

    /**
     * Conjunto de preços unitários para a tabulação atual
     */
    private LinkedList<Float> precosUnidade = new LinkedList();

    private int idStatus;

    /**
     * Descrição do status de uma tabulação. Exemplo: pendente.
     */
    String status;
    
    /**
     * Construtor que obriga a definição de uma configuração de tabulação.
     * @param configTabul Configuração da tabulação que está sendo criada.
     */
    public Tabulacao(ConfigNovaTabul configTabul)
    {
        configNovaTabul = configTabul;
    }

    /**
     * Método que descobre qual o maior preço da tabulação atual, tanto para caixa como para unidade.
     */
    private void calcularPrecoMax()
    {
        int posPrecoMax = 0;

        int tamanhoLista = precosCaixa.size();

        for (int i = 1; i < tamanhoLista; i++)
        {
            float precoMaxAtual = precosCaixa.get(posPrecoMax);
            float precoAtual = precosCaixa.get(i);

            if (precoAtual > precoMaxAtual)
            {
                posPrecoMax = i;
            }
        }

        // armazenando os máximos de preço
        estatisticasTabul.precoMaxC = precosCaixa.get(posPrecoMax);
        estatisticasTabul.precoMaxU = precosUnidade.get(posPrecoMax);
    }

    /**
     * Método que descobre qual o menor preço da tabulação atual, tanto para caixa como para unidade.
     * estatiscasTabul.posPrecoMin
     */
    private void calcularPrecoMin()
    {
        int posPrecoMin = 0;

        int tamanhoLista = precosCaixa.size();

        for (int i = 1; i < tamanhoLista; i++)
        {
            float precoMinAtual = precosCaixa.get(posPrecoMin);
            float precoAtual = precosCaixa.get(i);

            if (precoAtual < precoMinAtual)
            {
                posPrecoMin = i;
            }
        }

        // armazenando os mínimos de preço
        estatisticasTabul.precoMinC = precosCaixa.get(posPrecoMin);
        estatisticasTabul.precoMinU = precosUnidade.get(posPrecoMin);
    }

    /**
     * Método para cálculo da média do conjunto de preços.
     */
    private void calcularMedia()
    {
        int qtdeValores = this.precosCaixa.size();
        float total = 0;

        for (int i = 0; i < qtdeValores; i++)
        {
            float valorAtual = this.precosCaixa.get(i);
            total += valorAtual;
        }

        float media = total / qtdeValores;

        this.estatisticasTabul.mediaCaixa = media;
        this.estatisticasTabul.mediaUnidade = media / this.configNovaTabul.getQtdeCaixa();
    }

    /**
     * Método para cálculo da moda do conjunto de preços.
     */
    private void calcularModa()
    {
        int tamanhoLista = precosCaixa.size();

        // vetor que armazenará a frequência de cada preço na distribuição
        int[] freqPrecos = new int[tamanhoLista];

        // contagem da repetição de cada preço
        for (int i = 0; i < tamanhoLista; i++)
        {
            freqPrecos[i] = 1;

            for (int j = i + 1; j < tamanhoLista; j++)
            {
                float precoAtual = precosCaixa.get(i);
                float precoSeguinte = precosCaixa.get(j);

                if (precoAtual == precoSeguinte)
                {
                    freqPrecos[i]++;
                }
            }
        }

        boolean amodal = true;

        for (int k = 0; k < tamanhoLista; k++)
        {
            if (freqPrecos[k] != 1)
            {
                // há pelo menos um preço que se repete; logo, a frequência NÃO é amodal
                amodal = false;
            }
        }

        if (amodal == true)
        {
            // a sequência é amodal; modaCaixa e modaUnidade recebem uma constante que indica isso
            estatisticasTabul.modaCaixa = EstatisticasTabul.amodal;
            estatisticasTabul.modaUnidade = EstatisticasTabul.amodal;
        }
        else
        {
            // existe uma moda para a sequência; aqui, iremos a descobrir

            int posModa = 0;

            for (int l = 0; l < tamanhoLista; l++)
            {
                int freqAtual = freqPrecos[l];
                int freqMaxAtual = freqPrecos[posModa];

                if (freqAtual > freqMaxAtual)
                {
                    // a frequência do preço atual é maior; logo, o preço atual é melhor candidato à moda
                    posModa = l;
                }
                else if (freqAtual == freqMaxAtual)
                {
                    float valorAtual = precosCaixa.get(l);
                    float valorFreqMaxAtual = precosCaixa.get(posModa);

                    if (valorAtual > valorFreqMaxAtual)
                    {
                        // para frequências iguais, prevalece o maior valor como moda
                        posModa = l;
                    }
                }
            }

            // armazenando os valores de moda
            estatisticasTabul.modaCaixa = precosCaixa.get(posModa);
            estatisticasTabul.modaUnidade = precosUnidade.get(posModa);
        }
    }

    private void calcularMediana()
    {
        int qtdeTermos = this.precosCaixa.size();

        // ordenação dos preços de maneira crescente
        float[] precosCaixaOrd = new float[qtdeTermos];
        float[] precosUniOrd = new float[qtdeTermos];

        for (int i = 0; i < qtdeTermos; i++)
        {
            precosCaixaOrd[i] = this.precosCaixa.get(i);
            precosUniOrd[i] = this.precosUnidade.get(i);
        }

        Arrays.sort(precosCaixaOrd, 0, qtdeTermos);
        Arrays.sort(precosUniOrd, 0, qtdeTermos);

        if (qtdeTermos % 2 == 0)
        {
            // quantidade de termos par; mediana é a média dos termos centrais

            int indSegTermoCentral = qtdeTermos / 2; // índice do segundo termo central
            float segTermoCentral = precosCaixaOrd[indSegTermoCentral];
            float primTermoCentral = precosCaixaOrd[indSegTermoCentral - 1];

            float mediana = (primTermoCentral + segTermoCentral) / 2;
            this.estatisticasTabul.medianaCaixa = mediana;
            this.estatisticasTabul.medianaUnidade = mediana / this.configNovaTabul.getQtdeCaixa();
        }
        else
        {
            // quantidade de termos ímpar; mediana é o termo central

            int indTermoCentral = (qtdeTermos + 1) / 2;
            indTermoCentral--; // lembre-se: o primeiro valor é o de índice 0

            this.estatisticasTabul.medianaCaixa = precosCaixaOrd[indTermoCentral];
            this.estatisticasTabul.medianaUnidade = precosUniOrd[indTermoCentral];
        }
    }

    /**
     * Calcula as estatísticas(preço máximo, preço mínimo, moda, média e mediana) da tabulação atual.
     */
    public void calcularEstatisticas()
    {
        this.calcularPrecoMax();
        this.calcularPrecoMin();
        this.calcularMedia();
        this.calcularModa();
        this.calcularMediana();
    }

    public ConfigNovaTabul getConfigNovaTabul()
    {
        return configNovaTabul;
    }

    public void setConfigNovaTabul(ConfigNovaTabul configNovaTabul)
    {
        this.configNovaTabul = configNovaTabul;
    }

    /**
     * Adiciona um preço à lista de preços da caixa.
     * @param preco Preço de caixa a ser adicionado.
     */
    public void adicionarPrecoCaixa(float precoCaixa)
    {
        this.precosCaixa.add(precoCaixa);
    }

    public void setIdTabulacao(int idTabulacao)
    {
        this.idTabulacao = idTabulacao;
    }

    /**
     * Adiciona um preço à lista de preços unitários.
     * @param preco Preço de unidade a ser adicionado.
     */
    public void adicionarPrecoUnidade(float precoUnidade)
    {
        this.precosUnidade.add(precoUnidade);
    }

    public float getMediaCaixa()
    {
        return this.estatisticasTabul.mediaCaixa;
    }

    public float getMediaUnidade()
    {
        return this.estatisticasTabul.mediaUnidade;
    }

    public float getMedianaCaixa()
    {
        return this.estatisticasTabul.medianaCaixa;
    }

    public float getMedianaUnidade()
    {
        return this.estatisticasTabul.medianaUnidade;
    }

    public float getModaCaixa()
    {
        return estatisticasTabul.modaCaixa;
    }

    public float getModaUnidade()
    {
        return estatisticasTabul.modaUnidade;
    }

    public float getPrecoMaxCaixa()
    {
        return estatisticasTabul.precoMaxC;
    }

    public float getPrecoMaxUnidade()
    {
        return estatisticasTabul.precoMaxU;
    }

    public float getPrecoMinCaixa()
    {
        return estatisticasTabul.precoMinC;
    }

    public float getPrecoMinUnidade()
    {
        return estatisticasTabul.precoMinU;
    }

    public LinkedList<Float> getPrecosCaixa()
    {
        return precosCaixa;
    }

    public LinkedList<Float> getPrecosUnidade()
    {
        return precosUnidade;
    }

    public int getIdTabulacao()
    {
        return idTabulacao;
    }
}
